package com.eobard.obfuscate.core;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.eobard.adapter.ExtVisitorAdapter;
import com.eobard.config.obfuscate.ObfuscatorConfig;
import com.eobard.obfuscate.IObfuscator;
import com.eobard.params.RenameFieldParams;
import com.eobard.utils.CollectionUtils;
import com.eobard.utils.ExceptionUtils;
import com.eobard.utils.ObfuscatorUtils;
import com.eobard.utils.RandomExpression;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.ImportDeclaration;
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.PackageDeclaration;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.FieldDeclaration;
import com.github.javaparser.ast.expr.FieldAccessExpr;
import com.github.javaparser.ast.expr.NameExpr;
import com.github.javaparser.ast.visitor.Visitable;
import com.github.javaparser.resolution.declarations.ResolvedFieldDeclaration;
import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration;
import com.github.javaparser.resolution.types.ResolvedType;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @Author: Eobard Gu
 * @Date: 2025-08-12
 * @Description: 重命名字段核心混淆器:对类中的字段进行混淆,并把对应引用的地方一起重命名为新的随机值 <br>
 *
 * <font color=yellow>
 * eg:<br>修饰符  static/final/static final 基本类型/包装类型  字段名=值 <br>
 * ⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️<br>
 * 修饰符  static/final/static final 基本类型/包装类型  随机字段名=值
 * </font>
 */
@Component
public class RenameFieldObfuscator extends ExtVisitorAdapter<RenameFieldParams> implements IObfuscator {
    //文件根路径
    private static String sourceRoot;

    //包中所有文件的集合
    private static Map<String, CompilationUnit> fileMap = new HashMap<>();

    //需要重命名static、final、static final的字段类型列表
    private static final List<String> renameFieldTypeList = ObfuscatorUtils.allBasicPackagingType;

    /**
     * 字段定义位置相关
     **/
    // 类型名到CompilationUnit的映射，方便根据简单类名解析全限定名,k为包名.类名,v为当前java文件
    private static Map<String, CompilationUnit> typeToCompilationUnit = new HashMap<>();

    //字段映射map表,k为包名.类名.字段名,v为对应的字段声明地方
    private static Map<String, FieldDeclaration> fieldDefineMap = new HashMap<>();

    /**
     * 调用映射表相关
     **/
    //通过类名调用常量映射map表,k为包名.类名.字段名,v为对应的类名.字段名调用
    private static Map<String, FieldAccessExpr> classCallExprMap = new HashMap<>();

    //直接调用常量名,k为包名.类名.字段名,v为对应的字段名调用
    private static Map<String, NameExpr> callExprMap = new HashMap<>();

    //通过包名静态导入调用映射,k为包名.类名.字段名,v为当前类的所有静态导入的字段调用
    private static Map<String, List<NameExpr>> staticImportUsageMap = new HashMap<>();

    //更改名称的结果map,k为包名.类名.字段名,v为新名称
    private static Map<String, String> renameMap = new HashMap<>();

    /**
     * 已经生成过的字段名名缓存:如果缓存中存在那就需要重新生成,防止重复
     */
    private static Set<String> genFieldCache = new HashSet<>();

    //数据初始化代码
    @Override
    public void initialize(ObfuscatorConfig config) {
        ExceptionUtils.checkNonNull(config.getSourceRoot(), "请先设置项目路径!");

        //清空上一次记录
        CollectionUtils.clear(fileMap, typeToCompilationUnit, fieldDefineMap, classCallExprMap, callExprMap, staticImportUsageMap, renameMap);

        sourceRoot = config.getSourceRoot();
        initSolver(sourceRoot, config.getSourceExt());

        ExceptionUtils.checkNonNull(config.getRenameFieldParams().getBasePackage(), "请先配置【重命名字段名混淆器】的basePackage!");
        ExceptionUtils.checkNonNull(config.getRenameFieldParams().getPackageList(), "请先配置【重命名字段名混淆器】的包列表!");

        //获取源码文件
        ObfuscatorUtils.loopFiles(fileMap, sourceRoot);
    }


    //是否启动这个功能
    @Override
    public boolean supports(ObfuscatorConfig config) {
        return ObjectUtil.isNotEmpty(config.getRenameFieldParams());
    }


    //核心混淆代码
    @Override
    public void obfuscate(ObfuscatorConfig config) {
        //计算当前需要重命名的字段
        this.fileMap.forEach((path, cu) -> cu.accept(this, config.getRenameFieldParams()));

        //获取字段定义集合和类名.字段名调用的集合的交集:将公共的地方进行重命名
        Collection<String> intersection = CollectionUtil.intersection(fieldDefineMap.keySet(), classCallExprMap.keySet());
        //获取字段定义集合和字段名直接调用的集合的交集:将公共的地方进行重命名
        Collection<String> intersection2 = CollectionUtil.intersection(fieldDefineMap.keySet(), callExprMap.keySet());
        //获取字段定义集合和Java代码中静态导入的字段集合的交集:将公共的地方进行重命名
        Collection<String> intersection3 = CollectionUtil.intersection(fieldDefineMap.keySet(), staticImportUsageMap.keySet());

        //1.处理第一个交集
        intersection.forEach(i -> {
//            String newName = RandomExpression.getStaticFinalFiledName();
            String newName = getStaticFinalFiledName();
            FieldDeclaration declaration = fieldDefineMap.get(i);
            declaration.getVariables().get(0).setName(newName);
            classCallExprMap.get(i).setName(newName);
            //放入重命名集合中
            renameMap.put(i, newName);
        });
        //2.处理第二个交集
        intersection2.forEach(i -> {
            String newName = renameMap.computeIfAbsent(i, k -> {
                FieldDeclaration declaration = fieldDefineMap.get(k);
//                String genName = RandomExpression.getStaticFinalFiledName();
                String genName = getStaticFinalFiledName();
                declaration.getVariables().get(0).setName(genName);
                return genName;
            });
            NameExpr ne = callExprMap.get(i);
            if (ne != null) ne.setName(newName);
        });
        //3.处理第三个交集
        intersection3.forEach(i -> {
            String newName = renameMap.computeIfAbsent(i, k -> {
//                String genName = RandomExpression.getStaticFinalFiledName();
                String genName = getStaticFinalFiledName();
                fieldDefineMap.get(k).getVariables().get(0).setName(genName);
                return genName;
            });
            List<NameExpr> list = staticImportUsageMap.get(i);
            if (list != null) list.forEach(expr -> expr.setName(newName));
        });

        //4.处理更改导包信息
        updateStaticImports(renameMap, fileMap);

        //5.最终兜底检查:把项目中所有引用字段的地方全都替换成新名字
        applyRenameToAllUsages(renameMap);

        //6.保存更改文件
        ObfuscatorUtils.saveObfuscatedFiles(fileMap);
    }


    /**
     * ====================================callback函数=======================================================
     **/
    //收集所有的类的定义放入集合中,方便根据简单类名解析全限定名,k为包名.类名,v为当前java文件
    @Override
    public Visitable visit(ClassOrInterfaceDeclaration n, RenameFieldParams arg) {
        //获取当前类的源文件
        Optional<CompilationUnit> cuOpt = n.findCompilationUnit();
        if (cuOpt.isPresent()) {
            CompilationUnit cu = cuOpt.get();
            //获取所有的包名
            String pkg = cu.getPackageDeclaration().map(pd -> pd.getName().toString()).orElse("");
            List<String> names = new ArrayList<>();
            //从当前类开始往上找,如果是内部类就继续加入列表中,names:[inner,outer]
            ClassOrInterfaceDeclaration cur = n;
            while (true) {
                names.add(cur.getNameAsString());
                Optional<Node> parent = cur.getParentNode();
                if (parent.isPresent() && parent.get() instanceof ClassOrInterfaceDeclaration) {
                    cur = (ClassOrInterfaceDeclaration) parent.get();
                } else break;
            }
            //这里反转顺序,names:[outer,inner]
            Collections.reverse(names);
            //这里为了拼接outer.inner这种字段
            String classPart = String.join(".", names);
            //构建完整的包名.类名
            String fqcn = pkg.isEmpty() ? classPart : pkg + "." + classPart;
            typeToCompilationUnit.put(fqcn, cu);
        }
        return super.visit(n, arg);
    }


    //遍历出哪些字段可以被重命名:获取字段的声明,k为包名.类名.字段名,v为对应的字段声明地方
    @Override
    public Visitable visit(FieldDeclaration n, RenameFieldParams arg) {
        //获取类的定义文件
        Optional<CompilationUnit> optCu = n.findCompilationUnit();
        if (!optCu.isPresent()) return super.visit(n, arg);
        CompilationUnit cu = optCu.get();

        //判断当前字段是否为内部类,如果是内部类那么直接就pass掉
        Optional<Node> parentNode = n.getParentNode();
        if (ObfuscatorUtils.hasParentNode(parentNode, ClassOrInterfaceDeclaration.class)) {
            //如果不是内部类,那么获取当前字段所属类名
            ClassOrInterfaceDeclaration cd = (ClassOrInterfaceDeclaration) parentNode.get();

            //如果不是内部类那么继续判断是不是基本类型,只有基本类型的字段才能被重命名
            if (!ObfuscatorUtils.hasParentNode(cd.getParentNode(), ClassOrInterfaceDeclaration.class)) {
                String className = ObfuscatorUtils.getClassName(cu, true);
                //获取当前类的包声明信息
                String pkgName = cu.getPackageDeclaration().map(PackageDeclaration::getNameAsString).orElse("");

                //如果传进来的指定包不为空&&当前类的包名不在指定包中,那么就跳过
//                if (CollectionUtil.isNotEmpty(arg.getPackageList()) && !arg.getPackageList().contains(pkgName)) {
//                    return super.visit(n, arg);
//                }
                //如果传进来的指定包不为空&&当前类的包名不在指定包中,那么就跳过(支持子包)
                if (CollectionUtil.isNotEmpty(arg.getPackageList()) &&
                        arg.getPackageList().stream().noneMatch(pkg -> pkgName.equals(pkg) || pkgName.startsWith(pkg + "."))) {
                    return super.visit(n, arg);
                }

                //获取所有的声明变量
                n.getVariables().forEach(var -> {
                    //如果这个变量没有值,那就跳过重命名
                    if (!var.getInitializer().isPresent()) return;

                    //如果这个变量不是static或者final或者static final声明的直接跳过
                    if (!(n.isStatic() || n.isFinal())) return;

                    //获取字段类型
                    String typeName = n.getElementType().asString();
                    //判断是否为基本的数据类型
                    boolean isBasicType = renameFieldTypeList.contains(typeName);

                    //判断是否属于当前源码包类(防止外部jar的类改动)
                    boolean isLocalClassType = false;
                    if (!isBasicType) {
                        try {
                            ResolvedType type = n.getElementType().resolve();
                            if (type.isReferenceType()) {
                                String qualifiedName = type.asReferenceType().getQualifiedName();
                                if (qualifiedName.startsWith(arg.getBasePackage())) {
                                    isLocalClassType = true;
                                }
                            }
                        } catch (Exception e) {
                            return;
                        }
                    }
                    if (!(isBasicType || isLocalClassType)) return;

                    //获取变量字段的名称
                    String varName = var.getNameAsString();
                    //获取map的k值为:包名.类名.变量名
                    String key = genKey(className, varName);
                    fieldDefineMap.put(key, n);
                });

            }
        }
        return super.visit(n, arg);
    }

    //触发机制:通过类名来访问常量,这种情况下会触发FieldAccessExpr,k为包名.类名.字段名,v为对应的类名.字段名调用
    @Override
    public Visitable visit(FieldAccessExpr n, RenameFieldParams arg) {
        try {
            //获取`类名.常量`名调用对应的`包名.类名`信息
            String scopeType;
            try {
                scopeType = n.getScope().calculateResolvedType().describe();
            } catch (Exception e) {
                scopeType = resolveScopeToQualifiedName(n.getScope().toString(), n);
            }
            String key = genKey(scopeType, n.getNameAsString());
            classCallExprMap.put(key, n);
            // 如果已经存在重命名结果，立即替换
            if (renameMap.containsKey(key)) {
                n.setName(renameMap.get(key));
            }
        } catch (Exception ignored) {
        }
        return super.visit(n, arg);
    }

    //触发机制:直接访问静态常量的名称,这种情况下会触发NameExpr,k为包名.类名.字段名,v为对应的字段名调用
    @Override
    public Visitable visit(NameExpr n, RenameFieldParams arg) {
        //获取当前类的所有import包,将所有的导包的字段放入staticImportUsageMap中
        n.findCompilationUnit().ifPresent(cu -> {
            cu.getImports().forEach(importDecl -> {
                //如果是import static ...类型
                if (importDecl.isStatic()) {
                    if (!importDecl.isAsterisk()) {
                        //获取具体的名称
                        String importedFieldName = importDecl.getName().getIdentifier();

                        //如果import static导入的字段名称和当前的n一致,那就放入
                        if (importedFieldName.equals(n.getNameAsString())) {
                            String className = importDecl.getName().toString();
                            int lastDot = className.lastIndexOf('.');
                            if (lastDot > 0) {
                                String fullClassName = className.substring(0, lastDot);
                                String key = genKey(fullClassName, importedFieldName);
                                staticImportUsageMap.computeIfAbsent(key, k -> new ArrayList<>()).add(n);
                            }
                        }
                    } else {
                        //否则说明是通配符导入的import static ...*也需要放入
                        String importedClass = importDecl.getName().toString();
                        String key = genKey(importedClass, n.getNameAsString());
                        staticImportUsageMap.computeIfAbsent(key, k -> new ArrayList<>()).add(n);
                    }
                }
            });
        });
        //直接用n去解析直接获取类全名
        boolean resolvedAsField = false;
        try {
            //解析当前n是否为字段
            ResolvedValueDeclaration resolvedDecl = n.resolve();
            if (resolvedDecl.isField()) {
                ResolvedFieldDeclaration fieldDecl = resolvedDecl.asField();
                String declaringType = fieldDecl.declaringType().getQualifiedName();
                String key = genKey(declaringType, n.getNameAsString());
                callExprMap.put(key, n);
                resolvedAsField = true;
                //如果renameMap包含这个k,就直接重命名
                if (renameMap.containsKey(key)) n.setName(renameMap.get(key));
            }
        } catch (Exception ignored) {
        }

        //上面解析失败时的兜底回退解析方法
        if (!resolvedAsField) {
            String currentClass = getEnclosingClassQualifiedName(n);
            String potentialKey = genKey(currentClass, n.getNameAsString());
            if (fieldDefineMap.containsKey(potentialKey)) {
                callExprMap.put(potentialKey, n);
                if (renameMap.containsKey(potentialKey)) n.setName(renameMap.get(potentialKey));
            }
        }
        return super.visit(n, arg);
    }


    /**
     * ====================================后续检查函数=======================================================
     **/
    //处理更改导包信息
    private void updateStaticImports(Map<String, String> renameMap, Map<String, CompilationUnit> fileMap) {
        fileMap.values().forEach(cu -> {
            List<ImportDeclaration> toAdd = new ArrayList<>();
            List<ImportDeclaration> toRemove = new ArrayList<>();
            cu.getImports().forEach(importDecl -> {
                if (!importDecl.isStatic()) return;
                if (!importDecl.isAsterisk()) {
                    String fullImport = importDecl.getName().toString();
                    String importedFieldName = importDecl.getName().getIdentifier();
                    int lastDot = fullImport.lastIndexOf('.');
                    if (lastDot > 0) {
                        String className = fullImport.substring(0, lastDot);
                        String key = className + "." + importedFieldName;
                        if (renameMap.containsKey(key)) {
                            String newFieldName = renameMap.get(key);
                            String newImportNameStr = className + "." + newFieldName;
                            importDecl.setName(newImportNameStr);
                        }
                    }
                } else {
                    String className = importDecl.getName().toString();
                    List<String> matchedFields = renameMap.entrySet().stream()
                            .map(Map.Entry::getKey)
                            .filter(k -> k.startsWith(className + "."))
                            .collect(Collectors.toList());
                    if (!matchedFields.isEmpty()) {
                        toRemove.add(importDecl);
                        matchedFields.forEach(k -> {
                            String newField = renameMap.get(k);
                            ImportDeclaration newImport = new ImportDeclaration(className + "." + newField, true, false);
                            toAdd.add(newImport);
                        });
                    }
                }
            });
            toRemove.forEach(cu::remove);
            toAdd.forEach(cu::addImport);
        });
    }

    //最终检查:把项目中的所有引用字段的地方全部替换成新名字
    private void applyRenameToAllUsages(Map<String, String> renameMap) {
        if (renameMap.isEmpty()) return;

        //遍历每个java文件
        fileMap.values().forEach(cu -> {
            //找到所有通过 类名.字段名调用的来再次设置值防止还有没更新到的地方
            cu.findAll(FieldAccessExpr.class).forEach(fae -> {
                try {
                    String scopeQualified;
                    try {
                        scopeQualified = fae.getScope().calculateResolvedType().describe();
                    } catch (Exception e) {
                        scopeQualified = resolveScopeToQualifiedName(fae.getScope().toString(), fae);
                    }
                    //获取包名.类名
                    String key = genKey(scopeQualified, fae.getNameAsString());
                    if (renameMap.containsKey(key)) {
                        fae.setName(renameMap.get(key));
                    }
                } catch (Exception ignored) {
                }
            });

            //找到所有直接通过字段调用来再次设置值防止还有没更新到的地方
            cu.findAll(NameExpr.class).forEach(ne -> {
                try {
                    boolean updated = false;
                    //首先通过解析的方式来直接更改
                    try {
                        ResolvedValueDeclaration rvd = ne.resolve();
                        if (rvd.isField()) {
                            ResolvedFieldDeclaration rfd = rvd.asField();
                            String declaringType = rfd.declaringType().getQualifiedName();
                            String key = genKey(declaringType, ne.getNameAsString());
                            if (renameMap.containsKey(key)) {
                                ne.setName(renameMap.get(key));
                                updated = true;
                            }
                        }
                    } catch (Exception ignored) {
                    }

                    //如果直接解析的方式不能成功
                    if (!updated) {
                        // 那么就尝试隐式 this（最近类）
                        String enclosing = getEnclosingClassQualifiedName(ne);
                        if (enclosing != null && !enclosing.isEmpty()) {
                            String cand = enclosing;
                            while (cand != null && !cand.isEmpty()) {
                                String pk = genKey(cand, ne.getNameAsString());
                                if (renameMap.containsKey(pk)) {
                                    ne.setName(renameMap.get(pk));
                                    updated = true;
                                    break;
                                }
                                int idx = cand.lastIndexOf('.');
                                if (idx <= 0) break;
                                cand = cand.substring(0, idx);
                            }
                        }
                    }
                    if (!updated) {
                        String bareName = ne.getNameAsString();
                        for (Map.Entry<String, String> ent : renameMap.entrySet()) {
                            String oldKey = ent.getKey();
                            String oldFieldName = oldKey.substring(oldKey.lastIndexOf('.') + 1);
                            if (!oldFieldName.equals(bareName)) continue;
                            List<NameExpr> list = staticImportUsageMap.get(oldKey);
                            if (list != null && list.contains(ne)) {
                                ne.setName(ent.getValue());
                                updated = true;
                                break;
                            }
                        }
                    }
                } catch (Exception ignored) {
                }
            });
        });
    }

    /**
     * ==========================================工具函数============================================================
     **/

    //格式化类名
    private static String normalizeClassName(String raw) {
        if (raw == null) return "";
        raw = raw.trim();
        if (raw.startsWith("class ")) raw = raw.substring(6).trim();
        if (raw.startsWith("interface ")) raw = raw.substring(10).trim();
        int genericIdx = raw.indexOf('<');
        if (genericIdx > 0) raw = raw.substring(0, genericIdx);
        return raw;
    }

    //生成map集合的key值
    private static String genKey(String className, String fieldName) {
        return normalizeClassName(className) + "." + fieldName;
    }


    //获取包名.类名字符串
    private String resolveScopeToQualifiedName(String scopeText, Node contextNode) {
        if (scopeText == null) return "";
        scopeText = scopeText.trim();
        //如果已经带点,直接规范化返回即可
        if (scopeText.contains(".")) return normalizeClassName(scopeText);

        //如果是this或者super,就返回当前类的 包名.类名 信息
        if ("this".equals(scopeText) || "super".equals(scopeText)) {
            return getEnclosingClassQualifiedName(contextNode);
        }

        Optional<CompilationUnit> cuOpt = contextNode.findCompilationUnit();
        if (!cuOpt.isPresent()) return scopeText;
        CompilationUnit cu = cuOpt.get();

        // 从导包中寻找
        for (ImportDeclaration id : cu.getImports()) {
            if (id.isStatic()) continue;
            if (!id.isAsterisk()) {
                //eg: com.eobard.B
                String importName = id.getName().toString();
                //获取最后一个类名
                int last = importName.lastIndexOf('.');
                String simple = last >= 0 ? importName.substring(last + 1) : importName;
                //如果当前节点的导包中名称和scopeText一样,就返回当前的导包信息
                if (simple.equals(scopeText)) return importName;
            } else {
                // 通配符导入 import com.eobard.*
                String pkg = id.getName().toString();
                String candidate = pkg + "." + scopeText;
                //检查是不是项目源码的一部分
                if (typeToCompilationUnit.containsKey(candidate)) return candidate;
            }
        }

        // 如果没在import找到,那就检查是不是同一个包下的类
        String pkg = cu.getPackageDeclaration().map(pd -> pd.getName().toString()).orElse("");
        String cand = pkg.isEmpty() ? scopeText : (pkg + "." + scopeText);
        if (typeToCompilationUnit.containsKey(cand)) return cand;

        //如果都没找到,那就默认返回
        return scopeText;
    }

    //获取节点n的类限定名称
    private static String getEnclosingClassQualifiedName(Node n) {
        Optional<CompilationUnit> cuOpt = n.findCompilationUnit();
        String pkg = cuOpt.flatMap(cu -> cu.getPackageDeclaration().map(pd -> pd.getName().toString())).orElse("");
        Optional<ClassOrInterfaceDeclaration> anc = n.findAncestor(ClassOrInterfaceDeclaration.class);
        if (!anc.isPresent()) return pkg; // fallback
        ClassOrInterfaceDeclaration cid = anc.get();
        List<String> names = new ArrayList<>();
        ClassOrInterfaceDeclaration cur = cid;
        while (true) {
            names.add(cur.getNameAsString());
            Optional<Node> parent = cur.getParentNode();
            if (parent.isPresent() && parent.get() instanceof ClassOrInterfaceDeclaration) {
                cur = (ClassOrInterfaceDeclaration) parent.get();
            } else break;
        }
        Collections.reverse(names);
        String classPart = String.join(".", names);
        return pkg.isEmpty() ? classPart : (pkg + "." + classPart);
    }

    //检查新生成的字段名是否重复
    private static String getStaticFinalFiledName() {
        String newName = RandomExpression.getStaticFinalFiledName();
        while (genFieldCache.contains(newName)) {
            newName = RandomExpression.getStaticFinalFiledName();
        }
        genFieldCache.add(newName);
        return newName;
    }
}
